/*-------------------------------------------------------------------------
 *
 * 前端代码连接和断开数据库的功能。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/fe_utils/connect_utils.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres_fe.h"

#include "common/connect.h"
#include "common/logging.h"
#include "common/string.h"
#include "fe_utils/connect_utils.h"
#include "fe_utils/query_utils.h"

/*
 * 使用给定参数建立数据库连接。
 *
 * 如果需要并且 cparams->prompt_password 允许，将自动发出交互式密码提示。
 *
 * 如果 allow_password_reuse 为 true，我们将尝试重用在以前调用此例程时提供的任何密码。
 * （调用者不应该在与之前相同的数据库+用户重新连接时传递 allow_password_reuse=true，否则可能会造成密码泄露风险。）
 */
PGconn * connectDatabase(const ConnParams *fc_cparams, const char *fc_progname,
				bool fc_echo, bool fc_fail_ok, bool fc_allow_password_reuse)
{
	PGconn	   *fc_conn;
	bool		fc_new_pass;
	static char *fc_password = NULL;

	/* 调用者必须至少提供 dbname；其他参数可以为 NULL */
	Assert(fc_cparams->dbname);

	if (!fc_allow_password_reuse && fc_password)
	{
		free(fc_password);
		fc_password = NULL;
	}

	if (fc_cparams->prompt_password == TRI_YES && fc_password == NULL)
		fc_password = simple_prompt("Password: ", false);

	/*
	 * 开始连接。循环直到我们获得后端请求的密码。
	 */
	do
	{
		const char *fc_keywords[8];
		const char *fc_values[8];
		int			fc_i = 0;

		/*
		 * 如果 dbname 是连接字符串，它的条目可以覆盖从 cparams 获取的其他值；
		 * 但反过来，override_dbname 可以覆盖它的 dbname 组件。
		 */
		fc_keywords[fc_i] = "host";
		fc_values[fc_i++] = fc_cparams->pghost;
		fc_keywords[fc_i] = "port";
		fc_values[fc_i++] = fc_cparams->pgport;
		fc_keywords[fc_i] = "user";
		fc_values[fc_i++] = fc_cparams->pguser;
		fc_keywords[fc_i] = "password";
		fc_values[fc_i++] = fc_password;
		fc_keywords[fc_i] = "dbname";
		fc_values[fc_i++] = fc_cparams->dbname;
		if (fc_cparams->override_dbname)
		{
			fc_keywords[fc_i] = "dbname";
			fc_values[fc_i++] = fc_cparams->override_dbname;
		}
		fc_keywords[fc_i] = "fallback_application_name";
		fc_values[fc_i++] = fc_progname;
		fc_keywords[fc_i] = NULL;
		fc_values[fc_i++] = NULL;
		Assert(fc_i <= lengthof(fc_keywords));

		fc_new_pass = false;
		fc_conn = PQconnectdbParams(fc_keywords, fc_values, true);

		if (!fc_conn)
			pg_fatal("could not connect to database %s: out of memory",
					 fc_cparams->dbname);

		/*
		 * 没运气？尝试（再次）询问密码。
		 */
		if (PQstatus(fc_conn) == CONNECTION_BAD &&
			PQconnectionNeedsPassword(fc_conn) &&
			fc_cparams->prompt_password != TRI_NO)
		{
			PQfinish(fc_conn);
			if (fc_password)
				free(fc_password);
			fc_password = simple_prompt("Password: ", false);
			fc_new_pass = true;
		}
	} while (fc_new_pass);

	/* 检查以确保后端连接成功建立 */
	if (PQstatus(fc_conn) == CONNECTION_BAD)
	{
		if (fc_fail_ok)
		{
			PQfinish(fc_conn);
			return NULL;
		}
		pg_fatal("%s", PQerrorMessage(fc_conn));
	}

	/* 严格开始；调用者可以覆盖这一点。 */
	PQclear(executeQuery(fc_conn, ALWAYS_SECURE_SEARCH_PATH_SQL, fc_echo));

	return fc_conn;
}

/*
 * 尝试连接到适当的维护数据库。
 *
 * 这与 connectDatabase 的区别在于，它有一个规则来插入默认的 "dbname"，如果未提供的话
 * （这就是 cparams 不是常量的原因）。注意 cparams->dbname 通常应该来自
 * --maintenance-db 命令行参数。
 */
PGconn * connectMaintenanceDatabase(ConnParams *fc_cparams,
						   const char *fc_progname, bool fc_echo)
{
	PGconn	   *fc_conn;

	/* 如果指定了维护数据库名称，则直接连接到它。 */
	if (fc_cparams->dbname)
		return connectDatabase(fc_cparams, fc_progname, fc_echo, false, false);

	/* 否则，先尝试 postgres，然后是 template1。 */
	fc_cparams->dbname = "postgres";
	fc_conn = connectDatabase(fc_cparams, fc_progname, fc_echo, true, false);
	if (!fc_conn)
	{
		fc_cparams->dbname = "template1";
		fc_conn = connectDatabase(fc_cparams, fc_progname, fc_echo, false, false);
	}
	return fc_conn;
}

/*
 * 断开给定连接，取消任何活动的语句（如果有）。
 */
void disconnectDatabase(PGconn *fc_conn)
{
	char		fc_errbuf[256];

	Assert(fc_conn != NULL);

	if (PQtransactionStatus(fc_conn) == PQTRANS_ACTIVE)
	{
		PGcancel   *fc_cancel;

		if ((fc_cancel = PQgetCancel(fc_conn)))
		{
			(void) PQcancel(fc_cancel, fc_errbuf, sizeof(fc_errbuf));
			PQfreeCancel(fc_cancel);
		}
	}

	PQfinish(fc_conn);
}
